package jsonhttp

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"reflect"
)

type Client struct {
	http     *http.Client
	user     *string
	password *string
}

func New() *Client {
	return &Client{&http.Client{}, nil, nil}
}

func (c *Client) SetBasicAuth(u, p string) {
	c.user = &u
	c.password = &p
}

func (c *Client) doReq(req interface{}, method, url string, args ...interface{}) (*http.Response, error) {
	var encoded io.Reader
	if req != nil {
		b, err := json.Marshal(req)
		if err != nil {
			return nil, err
		}
		encoded = bytes.NewBuffer(b)
	}

	httpreq, err := http.NewRequest(method, fmt.Sprintf(url, args...), encoded)
	if err != nil {
		return nil, err
	}

	if c.user != nil && c.password != nil {
		httpreq.SetBasicAuth(*c.user, *c.password)
	}
	httpreq.Header.Add("Content-Type", "application/json")

	return c.http.Do(httpreq)
}

func (c *Client) Do(req interface{}, res interface{}, method, url string, args ...interface{}) error {
	httpres, err := c.doReq(req, method, url, args...)
	if httpres != nil {
		defer httpres.Body.Close()
	}
	if err != nil {
		return err
	}

	if httpres.StatusCode >= 400 {
		return errors.New(httpres.Status)
	}

	if res != nil {
		body, err := ioutil.ReadAll(httpres.Body)
		if err != nil {
			return err
		}

		err = json.Unmarshal(body, &res)
		if err != nil {
			return err
		}
	}

	return nil
}

func (c *Client) DoStream(req interface{}, res interface{}, sep byte, method, url string, args ...interface{}) (<-chan interface{}, error) {
	if res == nil {
		panic("cannot have nil res")
	}
	httpres, err := c.doReq(req, method, url, args...)
	if err != nil {
		if httpres != nil {
			httpres.Body.Close()
		}
		return nil, err
	}

	ch := make(chan interface{})
	go func() {
		defer close(ch)
		typ := reflect.TypeOf(res)
		reader := bufio.NewReader(httpres.Body)
		for {
			line, err := reader.ReadBytes(sep)
			if err != nil {
				log.Print(err)
				return
			}
			v := reflect.New(typ).Interface()
			err = json.Unmarshal(line, v)
			if err != nil {
				log.Print(err)
				return
			}

			ch <- v
		}

	}()

	return ch, nil
}

func (c *Client) Get(req interface{}, res interface{}, url string, args ...interface{}) error {
	return c.Do(req, res, "GET", url, args...)
}

func (c *Client) Post(req interface{}, res interface{}, url string, args ...interface{}) error {
	return c.Do(req, res, "POST", url, args...)
}

func (c *Client) Put(req interface{}, res interface{}, url string, args ...interface{}) error {
	return c.Do(req, res, "PUT", url, args...)
}
